第三章 Perl语言(四)-哈希、强制转换

哈希

哈希是一个聚合数据结构,初衷是为了实现将一组数据对应到另外一组数据。
哈希对数据的格式有点小小的要求:键必须是字符串,值是标量值。
其他编程语言也称这种数据结构为哈希表,关联数组,或字典什么的。

哈希最重要的特性就是:键是唯一的;键与键之间是无序的。

声明哈希

哈希使用记号%。声明一个哈希(词法作用域的):

my %favorite_flavors;

访问哈希单个元素使用记号$,并且用{ }包围键。

赋值:

my %favorite_flavors;
$favorite_flavors{Gabi} = 'Dark chocolate raspberry';
$favorite_flavors{Annette} = 'French vanilla';

my %favorite_flavors = (
'Gabi', 'Dark chocolate raspberry',
'Annette', 'French vanilla',
);

使用胖箭头会更清晰:

my %favorite_flavors = (
Gabi => 'Dark chocolate raspberry',
Annette => 'French vanilla',
);

胖箭头自动对前面的(裸字)字符进行引号操作:

sub name { 'Leonardo' }
my %address = (
name => '1123 Fib Place'
);
#并不会调用name()函数


#指明要调用name()函数
my %address = (
name() => '1123 Fib Place'
);

访问单个值:

my $address = $addresses{$name};

# 自动引起
my $address = $addresses{Victor};

# 不是有效的裸字,需要手动引起
my $address = $addresses{'Sue-Linn'};

# 使用“()”明确要函数调用,消除歧义
my $address = $addresses{get_name()};

哈希键必须是字符串,任何其他类型的都会被强制转成字符串:

for my $isbn (@isbns)
{
my $book = Book->fetch_by_isbn( $isbn );

$books{$book} = $book->price;    # 引用转换成字符串,然后作为键。这可能不是你想要的
}

哈希键的存在性

exists操作符用来侦测给定的键是否存在,存在则返回真,否则返回假:

my %addresses =
(
Leonardo => '1123 Fib Place',
Utako => 'Cantor Hotel, Room 1',
);
say "Have Leonardo's address"
if exists $addresses{Leonardo};
say "Have Warnie's address"
if exists $addresses{Warnie};

使用exists操作符有个优势:返回值只跟键有关,和键对应的值无关。

my %false_key_value = ( 0 => '' );
ok( %false_key_value,
'hash containing false key & value
should evaluate to a true value' );

访问哈希

访问键

for my $addressee (keys %addresses)
{
say "Found an address for $addressee!";
}

访问值

for my $address (values %addresses)
{
say "Someone lives at $address";
}

访问键值对

while (my ($addressee, $address) = each %addresses)
{
say "$addressee lives at $address";
}

注意:键值对与键值对之间没有特定次序。

每个哈希只有一个迭代记录,所以不要多次使用each。有些情况下,你可能需要重置记录器,那就在空语境下使用keys或values:

# 重置记录器
keys %addresses;
while (my ($addressee, $address) = each %addresses)
{
...
}

哈希切片

类似数组切片,就是一系列的键和值。
一次性赋值多个元素:

# %cats already contains elements
@cats{qw( Jack Brad Mars Grumpy )} = (1) x 4;
等同于:
my %cats = map { $_ => 1 }
qw( Jack Brad Mars Grumpy );

大括号明确表示你使用的是哈希切片:

my @buyer_addresses = @addresses{ @buyers };

合并2个哈希非常简单:

my %addresses = ( ... );
my %canada_addresses = ( ... );
@addresses{ keys %canada_addresses }
= values %canada_addresses;

如果有相同的key,会重写已经存在的。
另外,只有操作同一个哈希,并且在keys和values操作之间没有对哈希进行任何修改才能保证对应的顺序。

空哈希

空哈希就是指没有元素的哈希。在布尔语境中为假值。只要有元素就为真值(不管键或值的真假)。

use Test::More;
my %empty;
ok( ! %empty, 'empty hash should evaluate false' );
my %false_key = ( 0 => 'true value' );
ok( %false_key, 'hash containing false key
should evaluate to true' );


my %false_value = ( 'true key' => 0 );
ok( %false_value, 'hash containing false value
should evaluate to true' );
done_testing();

在标量语境中,哈希返回一个字符串,这个字符表示哈希内部的一些细节信息(哈希桶的信息),这与真假值是一致的,所以不用在意这些细节。
在列表语境中,返回的就是键值对列表。

习惯用法

由于哈希的特性,我们经常这样用:
去重一个列表

my %uniq;
undef @uniq{ @items };
my @uniques = keys %uniq;

# @items是需要去重的数据,@uniques是去重后的
# 对哈希切片使用undef就是将所有的切片值设置为undef。

计数

my %ip_addresses;
while (my $line = <$logfile>)
{
chomp $line;
my ($ip, $resource) = analyze_line( $line );
$ip_addresses{$ip}++;
...
}

缓存

{
my %user_cache;
sub fetch_user
{
my $id = shift;
$user_cache{$id} //= create_user($id);    # 如果没定义就赋值
return $user_cache{$id};
}
}

赋值默认值:

sub make_sundae
{
my %parameters = @_;
$parameters{flavor} //= 'Vanilla';
$parameters{topping} //= 'fudge';
$parameters{sprinkles} //= 100;
...
}


sub make_sundae
{
my %parameters =
(
flavor => 'Vanilla',
topping => 'fudge',
sprinkles => 100,
@_,
);
...
}

锁定哈希

有些时候你可能想要对哈希进行一些保护,比如不让别人修改你的哈希键或值;想让整个哈希为只读等。如果有这些需求,推荐你试试Hash::Util模块,肯定能满足你的要求。

强制转换

Perl中有些情形下会发生强制转换,比如以前提过的语境就可能会导致发生类型的强制转换。

布尔强制

比较典型的就是在条件表达式中,会强制转换成布尔类型。

字符串强制

使用字符串操作函数如eq,cmp,就会强制转换成字符串类型。

数字强制

使用数字操作符如==、 <、>就会强制转换成数字类型。

引用强制

使用解引用操作符会导致强制转换为引用类型。

my %users;
$users{Brad}{id} = 228;
$users{Jack}{id} = 229;
#创建哈希引用,并且赋值

缓存强制

Perl存储值时内部有2个插槽:字符串部分和数字部分。字符串化一个数值,并不会修改内部的数字部分,而是在内部字符串部分添加一个字符串值。相似的,数字化一个字符串,就会填充内部表示的数字部分而不会修改内部的字符串部分。
Perl的操作肯定有自己的倾向,到底优先使用哪一部分呢?布尔类型就更倾向字符串。比如,一个值可能已经缓存了内部表示(不是你期望的形式),这时候如果你使用隐式转换,可能结果不是你期望的。这是个极其罕见情形,几乎不会遇到。但是知道这个细节可能有一天能帮助到你。

双变量

Perl中的值在内部天然就不是由单一部分组成,通过上文我们已经知道了。通过双变量这种形式,用户能直接使用这个特性。系统模块Scalar::Util提供了函数dualvar(),通过这个函数我们能绕过Perl的强制策略(隐式转换),直接操作变量的字符串和数字部分:

use Scalar::Util 'dualvar';
my $false_name = dualvar 0, 'Sparkles & Blue';
say 'Boolean true!' if !! $false_name;
say 'Numeric false!' unless 0 + $false_name;
say 'String true!' if '' . $false_name;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容